package com.ruoyi.consumer.service.impl;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.order.WxPayMpOrderResult;
import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryResult;
import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.ruoyi.common.config.WxPayProperties;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.consumer.domain.PaymentRecord;
import com.ruoyi.consumer.domain.PaymentRecordRefund;
import com.ruoyi.consumer.domain.UserRoomRel;
import com.ruoyi.consumer.domain.VPaymentHy;
import com.ruoyi.consumer.domain.query.VPaymentHyQuery;
import com.ruoyi.consumer.domain.query.WechatPayQuery;
import com.ruoyi.consumer.domain.vo.WeChatUnifiedOderVo;
import com.ruoyi.consumer.enums.PaymentStatusEnums;
import com.ruoyi.consumer.mapper.PaymentHyMapper;
import com.ruoyi.consumer.mapper.PaymentRecordMapper;
import com.ruoyi.consumer.mapper.UserRoomRelMapper;
import com.ruoyi.consumer.service.IUserRoomRelService;
import com.ruoyi.consumer.service.IWechatPayService;
import com.ruoyi.consumer.service.SysFeeHyService;
import com.ruoyi.consumer.util.WechatPayUtils;
import com.ruoyi.system.service.ISysConfigService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.Instant;
import java.util.Collection;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 微信支付订单
 * @author lisongtao
 * @date 2023/11/7 23:54
 */
@Service("wechatPayService")
@RequiredArgsConstructor
public class WechatPayServiceImpl implements IWechatPayService {


    /**
     * 订单加锁HashMap
     */
    private final ConcurrentHashMap<String, Lock> orderLocks = new ConcurrentHashMap<>();

    @Autowired
    private ISysConfigService sysConfigService;
    @Autowired
    private PaymentHyMapper paymentHyMapper;
    @Autowired
    private UserRoomRelMapper userRoomRelMapper;
    @Autowired
    private RedisCache redisCache;
    @Autowired
    private WxPayProperties wxPayProperties;
    @Autowired
    private WxPayService wxService;
    @Autowired
    private PaymentRecordMapper paymentRecordMapper;
    @Autowired
    private IUserRoomRelService userRoomRelService;
    @Autowired
    private SysFeeHyService sysFeeHyService;

    /**
     * 微信支付预支付订单
     * 1、判断此支付计划是否存在
     * 2、判断此支付订单在redis中是否存在
     * 3、判断此支付订单在数据库中是否存在
     * 4、判断次支付订单支付金额是否大于0
     * @param payQuery
     */
    @Override
    public WeChatUnifiedOderVo unifiedOrder(WechatPayQuery payQuery) {
        // 校验支付计划是否可以进行支付
        VPaymentHy vPaymentHy = paymentPlanVerification(payQuery);
        // 支付年份
        String year = vPaymentHy.getYear();
        // 发起微信请求，进行预支付操作
        String planId = payQuery.getPlanId();
        // 唯一订单号
        String outTradeNo = WechatPayUtils.WECHAT_PAY_ORDER_PREFIX + planId + "_" + redisCache.getIncrementValue(StrUtil.format(WechatPayUtils.WECHAT_PAY_INCREMENT_KEY, year));
        // 用户信息
        String openId = payQuery.getOpenId();
        // 发送给微信的附属信息，支付记录主键
        // 调用微信生成预支付记录
        WxPayMpOrderResult wxPayMpOrderResult = null;
        try {
            wxPayMpOrderResult = weChatCreateOrder(vPaymentHy, outTradeNo, openId, planId);
        } catch (WxPayException e) {
            redisCache.deleteObject(StringUtils.format(WechatPayUtils.PLAN_ID_UNDER_PAY_STATUS, planId));
            throw new ServiceException(e.getErrCodeDes());
        }
        // 生成订单支付记录
        PaymentRecord paymentRecord = createPaymentRecord(wxPayMpOrderResult.getPackageValue(), vPaymentHy, outTradeNo);
        // 根据订单号存储在redis中
        redisCache.setCacheObject(StringUtils.format(WechatPayUtils.PLAN_DATA_PAY_IN_PAYMENT, outTradeNo), paymentRecord, 10, TimeUnit.DAYS);
        return WeChatUnifiedOderVo.of(wxPayMpOrderResult, outTradeNo);
//        return wxPayMpOrderResult;
    }


    /**
     * 支付计划校验工作
     */
    private VPaymentHy paymentPlanVerification(WechatPayQuery payQuery) {
        String paymentYear = sysFeeHyService.getPaymentYear();
        if (StringUtils.isBlank(paymentYear)) {
            throw new ServiceException("缴费年度设置错误，无法进行缴费，请稍后重试！");
        }
        String planId = payQuery.getPlanId();
        String roomId = payQuery.getRoomId();
        String openId = payQuery.getOpenId();

        // 进行支付计划是否存在判断
        if (redisCache.exists(StrUtil.format(WechatPayUtils.PLAN_ID_PAY_STATUS, planId))) {
            throw new ServiceException("当前缴费计划已完成支付！");
        }

        // 针对同一个支付id，多线程进行支付的情况进行加锁判断
        // 在取消支付或支付成功的情况下，删除redis标志
        Lock orderLock = orderLocks.computeIfAbsent(planId, k -> new ReentrantLock());
        orderLock.lock();
        try {
            // 处理订单逻辑
            if (redisCache.exists(StringUtils.format(WechatPayUtils.PLAN_ID_UNDER_PAY_STATUS, planId))) {
                throw new ServiceException("当前缴费计划正在支付中，请在点击查询订单状态按钮，查询支付结果！");
//                throw new ServiceException("当前缴费计划正在支付中，请在取消支付或支付超时(5分钟)的情况下重新发起支付！");
            }
            // 判断openId是否为当前用户
            SysUser user = SecurityUtils.getLoginUser().getUser();
            if (!openId.equals(user.getPublicOpenId())) {
                throw new ServiceException("当前登陆用户校验失败！");
            }

            // 再判断数据库中是否存在，并是否符合要求
            VPaymentHyQuery paymentHyQuery = new VPaymentHyQuery();
            paymentHyQuery.setPlanId(planId);
            VPaymentHy vPaymentHy = paymentHyMapper.selectVPaymentHyByPlanId(paymentHyQuery);
            if (vPaymentHy == null) {
                throw new ServiceException("查询支付记录失败，请稍后重试！");
            }
            // 判断房间编号
            String roomId1 = vPaymentHy.getRoomId();
            if (!roomId1.equals(roomId)) {
                throw new ServiceException("房源不匹配无法缴费！");
            }
            // 当前房源是否和当前用户有绑定关系
            UserRoomRel userRoomRel = new UserRoomRel();
            userRoomRel.setUserId(SecurityUtils.getUserId());
            userRoomRel.setRoomId(roomId);
            int countNum = userRoomRelMapper.countUserRoomRel(userRoomRel);
            if (countNum == 0) {
                throw new ServiceException("当前用户和当前房源暂无绑定关系，无法缴费！");
            }

            // 判断查询的支付状态
            String checked = vPaymentHy.getChecked();
            if ("1".equals(checked)) {
                throw new ServiceException("当前房源此年度供热费用已缴纳！");
            }

            // 二次判断年份是否相同
            String year = vPaymentHy.getYear();
            if (!paymentYear.equals(year)) {
                throw new ServiceException("缴费年费不正确，请稍后重试！");
            }

            String hmd = vPaymentHy.getHmd();
            String fyzt = vPaymentHy.getFyzt();
            String sfdb = vPaymentHy.getSfdb();
            // 判断是否为黑名单
            if ("黑名单".equals(hmd.trim())) {
                throw new ServiceException("缴费状态异常，请去营业厅办理办理！");
            }
            // 判断费用状态
            if (!("供热中".equals(fyzt) || "报停".equals(fyzt))) {
                throw new ServiceException("有陈欠未处理，请去营业厅办理办理！");
            }
            // 判断是否为低保用户
            if ("是".equals(sfdb)) {
                throw new ServiceException("低保用户不能网上缴费，请去营业厅办理办理！");
            }
            // 判断支付金额是否大于0
            BigDecimal amount = vPaymentHy.getAmount();
            if (!(amount != null && BigDecimal.ZERO.compareTo(amount) <= 0)) {
                throw new ServiceException("当前支付金额小于0，无法进行支付！");
            }
            redisCache.setCacheObject(StringUtils.format(WechatPayUtils.PLAN_ID_UNDER_PAY_STATUS, planId), "1", 10, TimeUnit.DAYS);
            return vPaymentHy;
        } finally {
            orderLock.unlock();
        }
    }


    /**
     * 生成微信预支付订单
     *
     * @author lisongtao
     * @date 2023/11/8 17:35
     * @param vPaymentHy
     * @param outTradeNo
     * @param openId
     * @return
     */
    private WxPayMpOrderResult weChatCreateOrder(VPaymentHy vPaymentHy, String outTradeNo, String openId, String planId) throws WxPayException {
        BigDecimal totalPrice = vPaymentHy.getAmount();
        String year = vPaymentHy.getYear();
        String owner = vPaymentHy.getOwner();

        // 发起微信请求，进行预支付操作
        WxPayUnifiedOrderRequest request = new WxPayUnifiedOrderRequest();
        request.setNotifyUrl(wxPayProperties.getNotifyUrl());
        request.setOutTradeNo(outTradeNo);
        request.setTotalFee(totalPrice.multiply(new BigDecimal("100")).intValue());
        // 商品描述
        request.setBody(year + "年度" + owner + "供热缴费");
        request.setSpbillCreateIp(IpUtils.getIpAddr());
        request.setAttach(planId);
        request.setOpenid(openId);
        request.setTradeType(WxPayConstants.TradeType.JSAPI);
        return wxService.createOrder(request);
    }


    /**
     * 生成微信支付记录
     *
     * @author lisongtao
     * @date 2023/11/8 17:40
     * @param packageValue  预支付id prepay_id=wx21201855730335ac86f8c43d1889123400
     * @param vPaymentHy    支付信息
     * @return
     */
    private PaymentRecord createPaymentRecord(String packageValue, VPaymentHy vPaymentHy, String outTradeNo) {
        if (StringUtils.isBlank(packageValue)) {
            throw new ServiceException("预支付id生成失败！");
        }
        String[] split = packageValue.split("=");
        if (split.length != 2) {
            throw new ServiceException("预支付id生成失败！");
        }
        String prepayId = split[1];

        // 当前登陆用户
        LoginUser loginUser = SecurityUtils.getLoginUser();
        // 生成支付记录
        PaymentRecord paymentRecord = getPaymentRecord(vPaymentHy, outTradeNo);
        paymentRecord.setPrepayId(prepayId);
        paymentRecord.setUserId(loginUser.getUserId());
        paymentRecord.setCreateBy(loginUser.getUsername());
        return paymentRecord;
    }

    /**
     * 微信支付回调
     * 判断状态当前订单是否为支付成功
     * 将支付成功的记录设置在redis中
     * 解密获取到支付成功的唯一订单id，将次记录设置为支付成功，软删掉当前订单其他的支付中记录
     *
     * @author lisongtao
     * @date 2023/11/8 23:27
     * @param xmlData
     * @return
     */
    @Override
    public void weChatPayNotify(String xmlData) throws WxPayException {
        final WxPayOrderNotifyResult notifyResult = wxService.parseOrderNotifyResult(xmlData);
        // 唯一支付订单号
        String outTradeNo = notifyResult.getOutTradeNo();
        // 防重复
        if (redisCache.exists(StrUtil.format(WechatPayUtils.OUT_TRADE_NO_PAY_STATUS, outTradeNo))) {
            return;
        }
        // 设置为当前订单号的状态设置为已支付，防止微信端多次重发
        redisCache.setCacheObject(StrUtil.format(WechatPayUtils.OUT_TRADE_NO_PAY_STATUS, outTradeNo), "1", 10, TimeUnit.DAYS);
        String planId = notifyResult.getAttach();

        // 将支付计划id设置为已支付，防止重复提交的情况
        redisCache.setCacheObject(StrUtil.format(WechatPayUtils.PLAN_ID_PAY_STATUS, planId), "1", 10, TimeUnit.DAYS);

        // 生成支付记录
        PaymentRecord paymentRecord = redisCache.getCacheObject(StringUtils.format(WechatPayUtils.PLAN_DATA_PAY_IN_PAYMENT, outTradeNo));
        if (paymentRecord == null) {
            // 如果此时redis中已经没有数据,查询数据从数据库
            VPaymentHyQuery paymentHyQuery = new VPaymentHyQuery();
            paymentHyQuery.setPlanId(planId);
            VPaymentHy vPaymentHy = paymentHyMapper.selectVPaymentHyByPlanId(paymentHyQuery);
            if (vPaymentHy != null) {
                paymentRecord = getPaymentRecord(vPaymentHy, outTradeNo);
            } else {
                paymentRecord = getPaymentRecordWithNo(outTradeNo);
            }
        }
        // 调用从库sql
        // 修改T_Room_hy表支付成功状态
        paymentHyMapper.updateRoomHyByRoomId(paymentRecord);
        // 修改T_Plan_hy表支付成功状态
        paymentHyMapper.updatePlanHyByPlanId(planId);
        // T_Payment_hy插入支付成功记录
        paymentHyMapper.insertPaymentHy(paymentRecord);

        // 保存记录
        paymentRecord.setDeviceInfo(notifyResult.getDeviceInfo());
        paymentRecord.setTransactionId(notifyResult.getTransactionId());
        paymentRecord.setTradeType(notifyResult.getTradeType());
        paymentRecord.setBankType(notifyResult.getBankType());
        paymentRecord.setCouponFee(notifyResult.getCouponFee() == null ? null : notifyResult.getCouponFee().toString());
        paymentRecord.setCallbackTime(new Date());
        paymentRecord.setPaymentStatus(notifyResult.getResultCode());
        paymentRecordMapper.insertPaymentRecord(paymentRecord);

        redisCache.deleteObject(StrUtil.format(WechatPayUtils.PLAN_DATA_PAY_IN_PAYMENT, outTradeNo));
        redisCache.deleteObject(StrUtil.format(WechatPayUtils.PLAN_ID_UNDER_PAY_STATUS, planId));
    }

    /**
     * 轮询支付记录
     * @author lisongtao
     * @date 2023/11/9 23:30
     */
    @Override
    public void pollPaymentRecords() {
        Collection<String> keys = redisCache.keys(StringUtils.format(WechatPayUtils.PLAN_DATA_PAY_IN_PAYMENT, "*"));
        for (String key : keys) {
            PaymentRecord paymentRecord = redisCache.getCacheObject(key);
            Date createTime = paymentRecord.getCreateTime();
            String planId = paymentRecord.getPlanId();
            Date nowDate = new Date();
            Instant instantCreateTime = createTime.toInstant();
            Instant instantNowDate = nowDate.toInstant();
            Duration duration = Duration.between(instantCreateTime, instantNowDate);
            if (duration.toMinutes() >= 5) {
                // 只对支付结果5分钟以上的记录进行判断
                try {
                    WxPayOrderQueryResult wxPayOrderQueryResult = wxService.queryOrder(null, paymentRecord.getOutTradeNo());
                    // SUCCESS：支付成功 REFUND：转入退款 NOTPAY：未支付 CLOSED：已关闭
                    paymentRecord.setPaymentStatus(wxPayOrderQueryResult.getTradeState());
                    paymentRecord.setCallbackTime(new Date());
                    paymentRecord.setUpdateTime(new Date());
                    paymentRecord.setUpdateBy("定时关闭");
                    paymentRecordMapper.insertPaymentRecord(paymentRecord);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    redisCache.deleteObject(key);
                    redisCache.deleteObject(StrUtil.format(WechatPayUtils.PLAN_ID_UNDER_PAY_STATUS, planId));
                }
            }
        }
    }

    @Override
    public void queryUnderPayOrder(String planId) {
        if (StringUtils.isBlank(planId)) {
            throw new ServiceException("支付计划不能为空！");
        }
        // 从支付查询此订单是否存在
        String redisKey = StringUtils.format(WechatPayUtils.PLAN_ID_UNDER_PAY_STATUS, planId);
        String value = redisCache.getCacheObject(redisKey);
        if (!"1".equals(value)) {
            redisCache.deleteObject(redisKey);
            return;
        }
        String outTradeNoRedisKey = StringUtils.format(WechatPayUtils.PLAN_DATA_PAY_IN_PAYMENT, "h_y_" + planId + "*");
        Collection<String> keys = redisCache.keys(outTradeNoRedisKey);
        for (String key : keys) {
            PaymentRecord paymentRecord = redisCache.getCacheObject(key);
            if (paymentRecord != null) {
                try {
                    String outTradeNo = paymentRecord.getOutTradeNo();
                    WxPayOrderQueryResult wxPayOrderQueryResult = wxService.queryOrder(null, outTradeNo);
                    // 如果状态不为成功，关闭订单
                    if (!PaymentStatusEnums.SUCCESS.name().equals(wxPayOrderQueryResult.getTradeState())) {
                        closeOrder(paymentRecord.getOutTradeNo(), "查询");
                    } else {
                        paymentRecord.setPaymentStatus(wxPayOrderQueryResult.getTradeState());
                        paymentRecord.setCallbackTime(new Date());
                        paymentRecord.setUpdateTime(new Date());
                        paymentRecord.setUpdateBy("查询支付中");
                        paymentRecordMapper.insertPaymentRecord(paymentRecord);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    redisCache.deleteObject(key);
                    redisCache.deleteObject(StrUtil.format(WechatPayUtils.PLAN_ID_UNDER_PAY_STATUS, planId));
                }
            }
        }
    }

    /**
     * 取消支付，删除redis记录
     * @author lisongtao
     * @param outTradeNo
     */
    @Override
    public void closeOrder(String outTradeNo, String type) throws WxPayException {
        if (StringUtils.isBlank(outTradeNo)) {
            throw new ServiceException("支付单号不能为空！");
        }
        // 关闭微信端订单
        wxService.closeOrder(outTradeNo);
        // 生成支付记录
        String key = StringUtils.format(WechatPayUtils.PLAN_DATA_PAY_IN_PAYMENT, outTradeNo);
        PaymentRecord paymentRecord = redisCache.getCacheObject(key);
        if (paymentRecord != null) {
            paymentRecord.setIsDelete("1");
            paymentRecord.setUpdateBy(SecurityUtils.getUsername() + "_" + type);
            paymentRecord.setUpdateTime(new Date());
            paymentRecord.setPaymentStatus(PaymentStatusEnums.CLOSED.name());
            paymentRecordMapper.insertPaymentRecord(paymentRecord);
            redisCache.deleteObject(StrUtil.format(WechatPayUtils.PLAN_ID_UNDER_PAY_STATUS, paymentRecord.getPlanId()));
            redisCache.deleteObject(key);
        }
    }

    /**
     * 退款接口
     */
    @Override
    public void refund(PaymentRecordRefund paymentRecordRefund) throws WxPayException {
        String id = paymentRecordRefund.getId();
        BigDecimal refundPrice = paymentRecordRefund.getRefundPrice();
        if (refundPrice.compareTo(BigDecimal.ZERO) <= 0) {
            throw new ServiceException("退款金额不能小于0！");
        }
        PaymentRecord paymentRecord = paymentRecordMapper.selectPaymentRecordById(id);
        if (paymentRecord == null) {
            throw new ServiceException("查询支付记录为空！");
        }
        // 支付总金额
        BigDecimal totalPrice = paymentRecord.getTotalPrice();
        if (refundPrice.compareTo(totalPrice) > 0) {
            throw new ServiceException("退款金额不能大于支付金额！");
        }
        String paymentStatus = paymentRecord.getPaymentStatus();
        if (!PaymentStatusEnums.SUCCESS.name().equals(paymentStatus)) {
            throw new ServiceException("支付状态不为支付成功，不能退款！");
        }
        String planId = paymentRecord.getPlanId();
        if (StringUtils.isBlank(planId)) {
            throw new ServiceException("支付计划编号不能为空！");
        }
        String year = paymentRecord.getPlanYear();
        if (StringUtils.isBlank(year)) {
            throw new ServiceException("支付年度不能为空！");
        }
        String outRefundNo = WechatPayUtils.WECHAT_PAY_ORDER_PREFIX + planId + "_" + redisCache.getIncrementValue(StrUtil.format(WechatPayUtils.WECHAT_REFUND_INCREMENT_KEY, year));
        WxPayRefundRequest wxPayRefundRequest = new WxPayRefundRequest();
        wxPayRefundRequest.setOutTradeNo(paymentRecord.getOutTradeNo());
        wxPayRefundRequest.setOutRefundNo(outRefundNo);
        wxPayRefundRequest.setTotalFee(totalPrice.multiply(new BigDecimal("100")).intValue());
        wxPayRefundRequest.setRefundFee(refundPrice.multiply(new BigDecimal("100")).intValue());
        wxPayRefundRequest.setOpUserId(SecurityUtils.getUserId().toString());
        WxPayRefundResult wxPayRefundResult = wxService.refundV2(wxPayRefundRequest);
        System.out.println(wxPayRefundResult.getResultCode());

        //        WxPayRefundResult refundV2(WxPayRefundRequest request) throws WxPayException;


    }

    /**
     * 根据{@link VPaymentHy} 购件支付记录
     */
    private PaymentRecord getPaymentRecord(VPaymentHy vPaymentHy, String outTradeNo) {
        PaymentRecord paymentRecord = new PaymentRecord();
        paymentRecord.setId(IdUtil.getSnowflakeNextIdStr());
        paymentRecord.setOutTradeNo(outTradeNo);
        paymentRecord.setPlanYear(vPaymentHy.getYear());
        paymentRecord.setPlanId(vPaymentHy.getPlanId());
        paymentRecord.setHeatNumber(vPaymentHy.getCardNo());
        paymentRecord.setHomeOwner(vPaymentHy.getOwner());
        paymentRecord.setTelNo(vPaymentHy.getTelNo());
        paymentRecord.setTotalPrice(vPaymentHy.getAmount());
        paymentRecord.setLocationName(vPaymentHy.getParent());
        paymentRecord.setRegionName(vPaymentHy.getRegion());
        paymentRecord.setUnitName(vPaymentHy.getUnit());
        paymentRecord.setRoomName(vPaymentHy.getRoom());
        paymentRecord.setRoomId(vPaymentHy.getRoomId());
        paymentRecord.setRoomCode(vPaymentHy.getRoomCode());
        paymentRecord.setUserPaymentStatus(StringUtils.isBlank(vPaymentHy.getStop()) ? "0" : vPaymentHy.getStop());
        paymentRecord.setCreateTime(new Date());
        return paymentRecord;
    }

    /**
     * 根据{@link VPaymentHy} 购件支付记录
     */
    private PaymentRecord getPaymentRecordWithNo(String outTradeNo) {
        PaymentRecord paymentRecord = new PaymentRecord();
        paymentRecord.setId(IdUtil.simpleUUID());
        paymentRecord.setOutTradeNo(outTradeNo);
        paymentRecord.setCreateTime(new Date());
        paymentRecord.setUserPaymentStatus("0");
        return paymentRecord;
    }


}
